﻿using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Common;
using UnityEngine;


#pragma warning disable 0219
#pragma warning disable 0168
#pragma warning disable 0162
namespace Network {

    public class MessageData {

        static MessageData() {
            SimplePool<MessageData>.CtorFunc = ( m ) => {
                m.id = 0;
                m.data = null;
                m.dataSize = 0;
            };
            SimplePool<MessageData>.DtorFunc = ( m ) => {
                m.id = 0;
                m.data = null;
                m.dataSize = 0;
            };
            SimplePool<MessageData>.CreateInstance = () => {
                return new MessageData();
            };
        }
   
        public static void ResetPool() {
            SimplePool<MessageData>.sharedInstance.Sweep();
        }

        public static MessageData Allocate() {
            return SimplePool<MessageData>.sharedInstance.Allocate();
        }
        
        public static void Free( ref MessageData m ) {
            SimplePool<MessageData>.sharedInstance.Free( ref m );
            m = null;
        }

        public const int HeaderBufferSize = 4;

        public UInt16 id = 0;
        public UInt16 dataSize = 0;
        public Byte[] data = null;

        private static int nextSendIndex = 0;
        private static int nextRecvIndex = 0;

        public static int GenNextSendIndex() {
            int ret = nextSendIndex++;
            nextSendIndex &= 0x1F;
            return ret;
        }

        public static int GenNextRecvIndex() {
            int ret = nextRecvIndex++;
            nextRecvIndex &= 0x1F;
            return ret;
        }

        public static void ClearIndexCounter() {
            nextSendIndex = 0;
            nextRecvIndex = 0;
        }

        public static UInt16 AttachNextPackageIndex( UInt16 id ) {
            return (UInt16)( ( id & ~0xF800 ) | ( GenNextSendIndex() << 11 ) );
        }

        public static UInt16 AttachCurrentPackageIndex( UInt16 id ) {
            return (UInt16)( ( id & ~0xF800 ) | ( nextSendIndex << 11 ) );
        }

        public bool FromReceivedBuf( byte[] buf ) 
		{
            if ( buf.Count() < HeaderBufferSize ) {
                return false;
            }
            dataSize = BitConverter.ToUInt16( buf, 0 );
            UInt16 _id = BitConverter.ToUInt16( buf, 2 );
            id = (UInt16)( _id & ~0xF800 );
            int index = ( _id & 0xF800 ) >> 11;
            if ( index != GenNextRecvIndex() ) {
                System.Diagnostics.Debug.Assert( false, "Received package index is incorrect!" );
            }
            dataSize -= HeaderBufferSize;
            return true;
        }
    }

    public class NetClient {

        public enum NetworkState {
            NetworkInit = 0,
            NetworkConnecting,
            NetworkConnectTimeOut,
            NetworkConnected,
            NetworkErr,
            NetworkClose,
        }
        #region const
        const int ConnectTimeout = 10;
        const float KMsgTimeOut = 2000000.0f;
        const int MaxPkgPerFrame = 30;
        const int MaxBufferSize = 20480;
        const String DefaultIP = "127.0.0.1";
        const int DefaultPort = 8080;
        #endregion

        #region fields
        String m_ip = DefaultIP;
        int m_port = DefaultPort;
        bool m_rLogMsg = false;
        bool m_wLogMsg = false;
        Action<NetworkState> m_netStateChangedCallback = null;
        HashSet<UInt32> m_tracingMsgIds = new HashSet<UInt32>();
        Queue<KeyValuePair<Byte[], int>> m_writeMsgQueue = new Queue<KeyValuePair<Byte[], int>>();
        Queue<MessageData> m_readMsgQueue = new Queue<MessageData>();
        NetworkState m_state = NetworkState.NetworkInit;
        int m_recvBytes = 0;
        byte[] m_recvBuffer = null;
        float m_runTime = 0;
        float m_connectTime = 0;

        bool m_hasHeader = false;
        byte[] m_tempMsgHeader = new byte[MessageData.HeaderBufferSize];
        MessageData m_tempData = null;

        CSocket m_socket = null;
        #endregion

        #region accessors
        public string ip {
            get { return m_ip; }
            set { m_ip = value; }
        }

        public int port {
            get { return m_port; }
            set { m_port = value; }
        }

        public bool isConnected {
            get {
                return m_socket != null && m_socket.Connected;
            }
        }

        public bool isShutDown {
            get {
                return m_socket == null || m_socket.GetState() == SocketState.StateShutDown;
            }
        }

        public bool logMessageEnable {
            get {
                return m_rLogMsg;
            }
            set {
                m_rLogMsg = value;
                m_wLogMsg = value;
            }
        }

        public bool netStateCallBack {
            get {
                return m_netStateChangedCallback != null;
            }
        }

        public event Action<NetworkState> onStateChanged {
            add {
                m_netStateChangedCallback += value;
            }
            remove {
                m_netStateChangedCallback -= value;
            }
        }

        public bool SetTracingMsgs( String msgs ) {
            msgs = msgs.Trim();
            String[] ids = msgs.Split( ',', ' ', ';' );
            m_tracingMsgIds.Clear();
            if ( ids != null ) {
                Array.ForEach( ids,
                    s => {
                        var t = typeof( Network.Messages.MessageType );
                        var name = "MSG_" + s;
                        try {
                            var _msgId = Enum.Parse( t, name, true );
                            if ( _msgId != null ) {
                                m_tracingMsgIds.Add( (UInt32)(Network.Messages.MessageType)_msgId );
                            }
                        } catch ( Exception ex) {
                            Common.ULogFile.sharedInstance.LogExceptionEx(LogFile.Exception, ex);
                        }
                    }
                );
            }
            return true;
        }

        #endregion

        public MessageData PeekMessage() {
            if ( m_readMsgQueue.Count > 0 ) {
                MessageData val = m_readMsgQueue.Peek();
                return val;
            }
            return null;
        }

        public void PopMessage() {
            if ( m_readMsgQueue.Count > 0 ) {
                var m = m_readMsgQueue.Peek();
                Common.BlockPool.sharedInstance.Free( ref m.data );
                MessageData.Free( ref m );
                m_readMsgQueue.Dequeue();
            }
        }

        public void PostMessage( Byte[] msg, int len ) {
            m_writeMsgQueue.Enqueue( new KeyValuePair<Byte[],int>( msg, len ) );
        }

        public void Initialize() {
            m_recvBuffer = new byte[MaxBufferSize];
            m_recvBytes = 0;
            m_writeMsgQueue = new Queue<KeyValuePair<Byte[], int>>();
            m_readMsgQueue = new Queue<MessageData>();
            m_state = NetworkState.NetworkInit;
        }

        public void Uninitialize() {
            Close( false );
            m_recvBuffer = null;
            m_recvBytes = 0;
            m_writeMsgQueue = null;
            m_readMsgQueue = null;
            m_hasHeader = false;
            m_tempData = null;
            MessageData.ResetPool();
        }

        public void ClearReceiveMsgQueue() {
            if ( m_readMsgQueue != null ) {
                m_readMsgQueue.Clear();
            }
        }

        public void Close( bool notify = true ) {
            if ( m_state == NetworkState.NetworkConnected ||
                m_state == NetworkState.NetworkConnecting ) {
                ChangeState( NetworkState.NetworkClose, notify );
            }
            MessageData.ClearIndexCounter();
            if ( m_socket != null ) {
                m_socket.Shutdown();
                m_socket = null;
            }
            m_writeMsgQueue.Clear();
            m_readMsgQueue.Clear();
            m_recvBytes = 0;
            m_runTime = 0;
            m_connectTime = 0;
            m_hasHeader = false;
            m_tempData = null;
            m_netStateChangedCallback = null;
        }

        public bool Connect( String ip = DefaultIP, int port = DefaultPort ) {
            Close();
            if ( isShutDown ) {
                m_ip = ip;
                m_port = port;
                try {
                    m_socket = new CSocket();
                    ChangeState( NetworkState.NetworkConnecting );
                    m_connectTime = m_runTime;
                    m_socket.ConnectSocket( ip, port );
                } catch ( Exception ex ) {
                    Common.ULogFile.sharedInstance.LogExceptionEx(LogFile.Exception, ex);
                }
            }
            return true;
        }

        #region Member Access
        public Socket socket {
            get {
                if ( m_socket != null )
                    return m_socket.socket;
                else
                    return null;
            }
            set { m_socket.socket = value; }
        }
        public SocketState socketState {
            get { return m_socket.socketState; }
            set { m_socket.socketState = value; }
        }
        public SocketError socketError {
            get { return m_socket.socketError; }
            set { m_socket.socketError = value; }
        }
        #endregion

        #region Sync Conn
        public void Connect( Action<bool> requestCallback = null, String ip = DefaultIP, int port = DefaultPort ) {
            Close();
            if ( isShutDown ) {
                m_ip = ip;
                m_port = port;
                try {
                    m_socket = new CSocket();
                    ChangeState( NetworkState.NetworkConnecting );
                    m_connectTime = m_runTime;
                    if ( requestCallback != null ) {
                        m_socket.ConnectSocket( ip, port, requestCallback );
                    } else {
                        m_socket.ConnectSocket( ip, port );
                    }

                } catch ( Exception ex ) {
                    Common.ULogFile.sharedInstance.LogExceptionEx(LogFile.Exception, ex);
                }
            }
        }
        #endregion

        public void ReflushSendQueue() {
            if ( m_socket != null ) {
                HandleMsgSendQueue( 0 );
            }
        }

        public void Update( float deltaTime ) {
            if ( m_socket != null ) {
                HandleMsgSendQueue( deltaTime );
            }
            if ( m_socket != null ) {
                UpdateImply();
            }
            if ( m_socket != null ) {
                HandleRecvBytes();
            }
        }

        private void HandleMsgSendQueue( float deltaTime ) {
            if ( m_state == NetworkState.NetworkConnected ) {
                if ( m_writeMsgQueue.Count > 0 ) {
                    m_runTime += deltaTime;
                    while ( m_writeMsgQueue.Count > 0 ) {
                        var buf = m_writeMsgQueue.Peek();
                        var data = buf.Key;
                        var len = buf.Value;
                        if ( !m_socket.Send( data, 0, len ) ) {
                            // send it next time
                            break;
                        }
                        Common.BlockPool.sharedInstance.Free( ref data );
                        m_writeMsgQueue.Dequeue();

                        if ( m_wLogMsg ) {
                            var dataSize = BitConverter.ToUInt16( data, 0 );
                            UInt16 _id = BitConverter.ToUInt16( data, 2 );
                            var id = (UInt16)( _id & ~0xF800 );
                            if ( m_tracingMsgIds.Count == 0 || m_tracingMsgIds.Contains( id ) ) {
                                Common.UDebug.LogEx( "Message Send: {0}", ( (Network.Messages.MessageType)id ).ToString() );
                            }
                        }
                    }
                }
            }
        }

        private void HandleRecvBytes() {
            for ( int i = 0; i < MaxPkgPerFrame; ++i ) {
                int iRead = m_socket.Recv( m_recvBuffer, m_recvBytes, MaxBufferSize - m_recvBytes );
                m_recvBytes += iRead;
                if ( 0 == m_recvBytes ) {
                    return;
                }
            }
            // at least has one message data in buffer 
            int offset = 0;
            int remain = m_recvBytes;
            for ( ; ; ) 
			{
            	if ( m_hasHeader ) 
				{
                    if ( m_tempData == null ) 
					{
                        m_tempData = MessageData.Allocate();
                        m_tempData.FromReceivedBuf( m_tempMsgHeader );
                        m_tempData.dataSize = (UInt16)( m_tempData.dataSize & 0x7fff );
                    }
                } 
				else 
				{
                    if ( remain >= MessageData.HeaderBufferSize ) 
					{
                        Buffer.BlockCopy( m_recvBuffer, offset, m_tempMsgHeader, 0, MessageData.HeaderBufferSize );
                        offset += MessageData.HeaderBufferSize;
                        remain -= MessageData.HeaderBufferSize;
                        m_hasHeader = true;
                        continue;
                    } else {
                        break;
                    }
                }

                if ( m_tempData != null ) 
				{
                    if ( m_tempData.dataSize > remain ) 
					{
                        break;
                    }

                    if ( m_tempData.dataSize > 0 ) 
					{
                        m_tempData.data = Common.BlockPool.sharedInstance.Allocate( m_tempData.dataSize );
                        bool compressed = ( m_tempData.dataSize & 0x8000 ) != 0;
                        if ( !compressed ) {
                            Buffer.BlockCopy( m_recvBuffer, offset, m_tempData.data, 0, m_tempData.dataSize );
                            offset += m_tempData.dataSize;
                            remain -= m_tempData.dataSize;
                            if ( m_rLogMsg ) {
                                var type = (Network.Messages.MessageType)m_tempData.id;
                                if ( m_tracingMsgIds.Count == 0 || m_tracingMsgIds.Contains( m_tempData.id ) ) {
                                    Common.UDebug.LogEx( "Message Received: {0} Time: " + ( DateTime.Now.Ticks / 10000 ).ToString(), type.ToString() );
                                }
                            }
                            m_readMsgQueue.Enqueue( m_tempData );
                            m_tempData = null;
                            m_hasHeader = false;
                        } else {
                            if ( m_rLogMsg ) 
							{
                                Common.UDebug.Log( "Compressed Message received!" );
                            }

                            using ( var msi = new MemoryStream( m_tempData.data ) ) 
							{
                                using ( var mso = new MemoryStream() ) 
								{
                                    using ( var gs = new GZipStream( msi, CompressionMode.Decompress ) ) 
									{
                                        int goffset = 0;
                                        int cnt = gs.Read( m_tempMsgHeader, goffset, m_tempMsgHeader.Count() );
                                        while ( cnt == m_tempMsgHeader.Count() ) 
										{
                                            goffset += cnt;
                                            MessageData m = MessageData.Allocate();
                                            m.FromReceivedBuf( m_tempMsgHeader );
                                            Common.UDebug.Log( String.Format( "Message[{0}] received, data size {1}", m.id, m.dataSize ) );
                                            if ( m.dataSize > 0 ) {
                                                m.data = Common.BlockPool.sharedInstance.Allocate( m.dataSize );
                                                cnt = gs.Read( m.data, goffset, m.dataSize );
                                                if ( cnt < m.data.Count() ) {
                                                    Common.UDebug.LogError( "Data corrupted!" );
                                                    break;
                                                }
                                                goffset += cnt;
                                            }
                                            if ( m_rLogMsg ) {
                                                var type = (Network.Messages.MessageType)m.id;
                                                if ( m_tracingMsgIds.Count == 0 || m_tracingMsgIds.Contains( m.id ) ) {
                                                    Common.UDebug.LogEx( "Message Received: {0}", type.ToString() );
                                                }
                                            }
                                            m_readMsgQueue.Enqueue( m );
                                            cnt = gs.Read( m_tempMsgHeader, goffset, m_tempMsgHeader.Count() );
                                        }
                                    }
                                }
                            }
                            Common.BlockPool.sharedInstance.Free( ref m_tempData.data );
                            MessageData.Free( ref m_tempData );
                            m_hasHeader = false;
                        }
                    } 
					else 
					{
                        // None data message
                        if ( m_rLogMsg ) 
						{
                            var type = (Network.Messages.MessageType)m_tempData.id;
                            if ( m_tracingMsgIds.Count == 0 || m_tracingMsgIds.Contains( m_tempData.id ) ) {
                                Common.UDebug.LogEx( "Message Received: {0}", type.ToString() );
                            }
                        }
                        m_readMsgQueue.Enqueue( m_tempData );
                        m_tempData = null;
                        m_hasHeader = false;
                    }
                }
            }
            // remove parsed data
            if ( offset > 0 ) 
			{
                Buffer.BlockCopy( m_recvBuffer, offset, m_recvBuffer, 0, remain );
                m_recvBytes = remain;
            }
        }

        void ChangeState( NetworkState state, bool notify = true ) {
            if ( m_state != state ) {
                var oldState = m_state;
                m_state = state;
                switch ( m_state ) {
                    case NetworkState.NetworkErr: {
                            if ( m_netStateChangedCallback == null ) 
                            {
                                Common.UDebug.LogError( "Network state Error, SocketErrorCode: " + m_socket.SocketErrorCode );
                            }
                        }
                        break;
                }
                if ( notify && m_netStateChangedCallback != null ) {
                    m_netStateChangedCallback( state );
                }
            }
        }

        private void UpdateImply() {
            m_socket.Update();
            if ( m_socket.SocketErrorCode != SocketError.Success ) {
                ChangeState( NetworkState.NetworkErr );
            }
            if ( NetworkState.NetworkConnecting == m_state ) {
                SocketState state = m_socket.GetState();
                if ( state == SocketState.StateConnected ) {
                    ChangeState( NetworkState.NetworkConnected );
                } else if ( state == SocketState.StateConnecting ) {
                    if ( m_runTime - m_connectTime > ConnectTimeout ) {
                        ChangeState( NetworkState.NetworkConnectTimeOut );
                    }
                } else if ( state == SocketState.StateSocketError ) {
                    ChangeState( NetworkState.NetworkErr );
                }
            }
        }
    }
}
